#include "guiterm.hpp"
#include <QStackedLayout>

FileView::FileView(RemoteJob *job)
	:JobViewSegment(job)
{
	this->listReceiver = NULL;
	this->listWidget = NULL;
	
	QSizePolicy sizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
	sizePolicy.setHeightForWidth(true);
	setSizePolicy(sizePolicy);
	
	layout = new QVBoxLayout();
		layout->setContentsMargins(0,0,0,0);
		layout->setSpacing(0);
		setLayout(layout);
	headerLayout = new QHBoxLayout();
		headerLayout->setContentsMargins(4,2,2,2);
		headerLayout->setSpacing(0);
		layout->insertLayout(0, headerLayout, 0);
	commandLabel = new QLabel();
		commandLabel->setPalette(*PaletteManager::inactivePrompt());
		headerLayout->addWidget(commandLabel);
		headerLayout->setStretchFactor(commandLabel, 1);
	settingsLink = new LinkWidget("options");
		connect(settingsLink, SIGNAL(clicked()), this, SLOT(openSettingsMenu()));
		headerLayout->addWidget(settingsLink);
}

FileView::~FileView()
{
}

void FileView::beginRefresh(ServerConnection *host, const RPCParams *params)
{
	command = LsCommand(params->getString("cmd"));
	commandLabel->setText(command.toCommand().c_str());
	
	path = Path(params->getString("path"));
	
	if(listReceiver)
		delete listReceiver;
	
	initListWidget(params);
	listReceiver = new LsListReceiver(host, params);
	connect(listReceiver, SIGNAL(filesAdded(const vector<FileEntry>&)),
	        listWidget, SLOT(filesAdded(const vector<FileEntry>&)));
}

void FileView::initListWidget(const RPCParams *params)
{
	if(listWidget)
		delete listWidget;
	
	if(command.getIsDetailed())
		listWidget = new DetailedFileView(params, width(), this);
	else
		listWidget = new ColumnFileView(params, width(), this);
	
	connect(listWidget, SIGNAL(doubleClicked(string,string)), this, SLOT(doubleClicked(string,string)));
	connect(listWidget, SIGNAL(pressed(string,string)), this, SLOT(pressed(string,string)));
	connect(listWidget, SIGNAL(selectionChanged(const vector<string>&)), this, SLOT(selectionChanged(const vector<string>&)));

	layout->addWidget(listWidget);
	listWidget->show();
}

void FileView::setDetailed(bool detailed)
{
	if(command.getIsDetailed() == detailed) return;
	command.setIsDetailed(detailed);
	requestRefresh();
}

void FileView::setShowHidden(bool showHidden)
{
	if(command.getShowHidden() == showHidden) return;
	command.setShowHidden(showHidden);
	requestRefresh();
}

void FileView::requestRefresh()
{
	// Update the label
	string labelText = command.toCommand();
	commandLabel->setText(labelText.c_str());
	
	// Clear the old file list
	delete listWidget;
	listWidget = NULL;
	listReceiver = NULL;
	
	// Request a new file list
	ServerConnection *host = session->getHost();
	int id = host->newTransferId();
	RPCParams params;
		params.set("id", id);
		params.set("cmd", command.toCommand());
	host->call("ls", &params);
	host->expectLsResponse(id, this);
}

string FileView::expandFileName(string fileName)
{
	return Path(fileName).resolve(path).toString();
}

LsCommand FileView::getCommand() const
{
	return command;
}

void FileView::setCommand(LsCommand command)
{
	this->command = command;
	requestRefresh();
}

void FileView::openSettingsMenu()
{
	LsSettingsWidget *settingsMenu = new LsSettingsWidget(&command, this);
	
	QSize menuSize = settingsMenu->sizeHint();
	QSize linkSize = settingsLink->size();
	QPoint botRightCorner = settingsLink->pos() + QPoint(linkSize.width(), linkSize.height());
	QPoint globalBotRightCorner = mapToGlobal(botRightCorner);
	QPoint popupLocation = QPoint(globalBotRightCorner.x() - menuSize.width(), globalBotRightCorner.y());
	
	settingsMenu->move(popupLocation);
	settingsMenu->show();
}

void FileView::doubleClicked(string fileName, string fileType)
{
	// TODO: Handle the special "N hidden files" entry
	
	if(fileType == "dir")
	{
		string cmd = "ls "+quoteProtectString(expandFileName(fileName));
		session->mousedCommand(cmd);
	}
	else
	{
		string cmd = "localview "+quoteProtectString(expandFileName(fileName));
		session->mousedCommand(cmd);
	}
}

void FileView::pressed(string fileName, string fileType)
{
	if(QApplication::mouseButtons() & Qt::RightButton) {
		FileContextMenu *menu = new FileContextMenu(expandFileName(fileName), session);
		menu->popup(QCursor::pos());
	}
}

void FileView::selectionChanged(const vector<string> &selectedFiles)
{
	CommandPrompt *prompt = session->getPrompt();
	if(prompt) prompt->setSelectedFiles(selectedFiles);
}

//////////////////////////////////////////////////////////////////////////////

LsListReceiver::LsListReceiver(RemoteHost *source, const RPCParams *params)
	:StreamReceiver(source, params)
{
}

void LsListReceiver::recv(int len, const char *data)
{
	buf.append(len, data);
	vector<FileEntry> files;

	int buflen = buf.getLen();
	const char *bufData = buf.getData();
	for(int ii=0; ii<buflen; )
	{
		if(bufData[ii] == '\n') {
			parseLine(bufData, ii, files);
			buf.popFront(ii+1);
			ii = 0;

			buflen = buf.getLen();
			bufData = buf.getData();
		} else {
			ii++;
		}
	}
	
	if(files.size()) {
		emit filesAdded(files);
	}
}

void LsListReceiver::parseLine(const char *data, int len, vector<FileEntry> &files)
{
	list<string> incomingFields = stringToStringList(string(data, len));

	FileEntry entry;
	entry.fields.insert(entry.fields.end(), incomingFields.begin(), incomingFields.end());
	files.push_back(entry);
}


void LsListReceiver::onClose()
{
	emit listFinished();
	this->deleteLater();
}

//////////////////////////////////////////////////////////////////////////////

FileListWidget::FileListWidget(const RPCParams *params, QWidget *parent)
	:QWidget(parent)
{
	list<string> fieldsList = stringToStringList(params->getString("fields"));
	fields.insert(fields.end(), fieldsList.begin(), fieldsList.end());
}

int FileListWidget::getFieldIndex(string field)
{
	for(size_t ii=0; ii<fields.size(); ii++)
		if(fields[ii] == field)
			return ii;
	return -1;
}

string FileListWidget::getFieldName(int index)
{
	return fields[index];
}

string FileListWidget::getFileName(const FileEntry *file)
{
	int fieldIndex = getFieldIndex("name");
	if(fieldIndex>=0 && fieldIndex<(int)file->fields.size())
		return file->fields[fieldIndex];
	else
		return "";
}

string FileListWidget::getFileType(const FileEntry *file)
{
	int fieldIndex = getFieldIndex("type");
	if(fieldIndex>=0 && fieldIndex<(int)file->fields.size())
		return file->fields[fieldIndex];
	else
		return "";
}

void FileListWidget::filesAdded(const vector<FileEntry> &files)
{
	int nameField = getFieldIndex("name");
	int typeField = getFieldIndex("type");
	
	for(size_t ii=0; ii<files.size(); ii++)
	{
		string name = files[ii].fields[nameField];
		string type = files[ii].fields[typeField];
		fileTypes[name] = type;
	}
}

//////////////////////////////////////////////////////////////////////////////

const char *columnDescriptions[] = { "perm", "links", "owner", "group", "size", "mtime", "name" };

DetailedFileView::DetailedFileView(const RPCParams *params, int width, QWidget *parent)
	:FileListWidget(params, parent)
{
	QVBoxLayout *layout = new QVBoxLayout();
		layout->setContentsMargins(0,0,0,0);
		layout->setSpacing(0);
		setLayout(layout);
	tree = new QTreeWidget();
		tree->setFrameStyle(QFrame::NoFrame);
		tree->setPalette(*PaletteManager::fileView());
		tree->setFont(config.font);
		
		tree->setRootIsDecorated(false);
		tree->setHeaderHidden(true);
		tree->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
		tree->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
		tree->setSelectionMode(QAbstractItemView::ExtendedSelection);
		
		tree->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored));
		layout->addWidget(tree);
	
	// Pick the columns and set their widths
	for(size_t ii=0; ii<sizeof(columnDescriptions)/sizeof(*columnDescriptions); ii++)
	{
		int field = getFieldIndex(columnDescriptions[ii]);
		if(field >= 0) {
			columnFields.push_back(field);
		}
	}
	tree->setColumnCount(columnFields.size());
	
	// Set column widths
	string colWidthsStr = params->getString("cols");
	vector<string> colWidths;
	separateArgs(colWidths, colWidthsStr.c_str());
	
	QFontMetrics fontMetrics(config.font);
	int charWidth = fontMetrics.maxWidth();
	
	for(size_t ii=0; ii<columnFields.size(); ii++)
	{
		int field = columnFields[ii];
		int chars = -1;
		sscanf(colWidths[field].c_str(), "%i", &chars);
		int colWidth = chars * charWidth;
		
		if(getFieldName(field) == "name") {
			// Add some extra width for the file type icon
			colWidth += 32; // TODO: Calculate how big this actually is
		} else {
			// Add a little space for margins (TODO: Determine properly how
			// much space is needed)
			colWidth += 8;
		}
		
		tree->setColumnWidth(ii, colWidth);
	}
	
	connect(tree, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(itemDoubleClicked(QTreeWidgetItem*, int)));
	connect(tree, SIGNAL(itemPressed(QTreeWidgetItem*, int)), this, SLOT(itemPressed(QTreeWidgetItem*, int)));
	connect(tree, SIGNAL(itemSelectionChanged()), this, SLOT(itemSelectionChanged()));
}

void DetailedFileView::itemDoubleClicked(QTreeWidgetItem *item, int column)
{
	int fieldIndex = getFieldIndex("name");
	string name = item->text(fieldIndex).toStdString();
	string type = fileTypes[name];
	emit doubleClicked(name, type);
}

void DetailedFileView::itemPressed(QTreeWidgetItem *item, int column)
{
	int fieldIndex = getFieldIndex("name");
	string name = item->text(fieldIndex).toStdString();
	string type = fileTypes[name];
	emit pressed(name, type);
}

void DetailedFileView::itemSelectionChanged()
{
	int fieldIndex = getFieldIndex("name");
	QList<QTreeWidgetItem*> selectedItems = tree->selectedItems();
	vector<string> fileNames;
	
	for(QList<QTreeWidgetItem*>::iterator ii=selectedItems.begin(); ii!=selectedItems.end(); ii++)
	{
		QTreeWidgetItem *item = *ii;
		string name = item->text(fieldIndex).toStdString();
		fileNames.push_back(name);
	}
	
	emit selectionChanged(fileNames);
}

void DetailedFileView::filesAdded(const vector<FileEntry> &files)
{
	FileListWidget::filesAdded(files);
	
	QList<QTreeWidgetItem*> items;
	
	for(size_t ii=0; ii<files.size(); ii++)
	{
		const FileEntry *file = &files[ii];
		
		QStringList columnText;
		for(size_t ii=0; ii<columnFields.size(); ii++) {
			int field = columnFields[ii];
			columnText.append(file->fields[field].c_str());
		}
		
		QTreeWidgetItem *item = new QTreeWidgetItem(columnText);
		
		// Some fields have special properties; 'links' and 'size' are
		// right-aligned, and 'name' has an icon.
		for(size_t ii=0; ii<columnFields.size(); ii++) {
			string fieldName = getFieldName(columnFields[ii]);
			if(fieldName=="size" || fieldName=="links")
				item->setTextAlignment(ii, Qt::AlignRight);
			else if(fieldName == "name") {
				QIcon icon = IconProvider::instance()->getIcon(file, this);
				item->setIcon(item->columnCount()-1, icon);
			}
		}
		
		items.append(item);
	}
	
	// Add the items we were notified about all at once, so the layout doesn't
	// have to be rebuilt multiple times
	tree->addTopLevelItems(items);
	tree->updateGeometry();
}

QSize DetailedFileView::sizeHint() const
{
	int rows = tree->topLevelItemCount();
	if(!rows) return QSize(0, 0);
	
	int rowHeight = tree->sizeHintForRow(0);
	return QSize(0, rows*rowHeight);
}

//////////////////////////////////////////////////////////////////////////////

ColumnLayout::ColumnLayout(QListWidget *list, int numFiles, string colSpec)
{
	setContentsMargins(0,0,0,0);
	setSpacing(0);
	
	this->numFiles = numFiles;
	this->list = list;
	
	// Determine the size and number of columns
	// We get from the server (as an RPC parameter) a list of how wide the
	// display is for each number of columns from 2 to LS_MAX_COLUMNS, in
	// characters, not including the icons or the space between columns. The
	// width of a column is the length of its longest element, in chars. All
	// columns have the same number of elements except the last one, which has
	// fewer, and elements are downloaded in order columnwise.
	vector<string> colWidthStrs; 
	separateArgs(colWidthStrs, colSpec.c_str());
	
	for(size_t ii=0; ii<colWidthStrs.size(); ii++)
		colWidths.push_back(atoi(colWidthStrs[ii].c_str()));
}

bool ColumnLayout::hasHeightForWidth() const
{
	return true;
}

int ColumnLayout::heightForWidth(int w) const
{
	if(!list || !list->count())
		return 0;
	
	int cols = 1;
	
	QFontMetrics fontMetrics(config.font);
	int charWidth = fontMetrics.maxWidth();
	
	// This is just a guess (more like an upper bound) representing the amount
	// of padding that QListWidget puts between columns. TODO: Figure out how to
	// retrieve the real value for this
	int colSpacing = 8;
	// The amount of space taken up by the icon and internal padding per column.
	// TODO: Figure out how to retrieve the real value for this.
	int colOverhead = 16;
	
	for(size_t ii=0; ii<colWidths.size(); ii++)
	{
		int width = colWidths[ii] * charWidth + (ii+1)*colSpacing + (ii+2)*colOverhead;
		if(width>w)
			break;
		cols = ii+2;
	}
	
	// Calculate number of rows
	int rows = (numFiles + cols-1) / cols;
	// If the first column isn't fully downloaded yet, restrict display height
	// to the amount of it which has been downloaded
	if(rows > list->count())
		rows = list->count();
	
	QListWidgetItem *firstItem = list->item(0);
	QRect rect = list->visualItemRect(firstItem);
	int rowHeight = rect.height();
	
	// Extra +1 height to make up for an off-by-one error somewhere in Qt
	return rows*rowHeight + 1;
}

//////////////////////////////////////////////////////////////////////////////

ColumnFileView::ColumnFileView(const RPCParams *params, int width, QWidget *parent)
	:FileListWidget(params, parent)
{
	// Set size policy to height-for-width
	QSizePolicy sizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
	sizePolicy.setHeightForWidth(true);
	setSizePolicy(sizePolicy);
	
	// Parse information from the header of the file list
	string colSpec = params->getString("cols");
	int numFiles = params->getInt("files");
	
	// Create layout
	list = new QListWidget();
	ColumnLayout *layout = new ColumnLayout(list, numFiles, colSpec);
	setLayout(layout);
	
	list->setFrameStyle(QFrame::NoFrame);
	list->setPalette(*PaletteManager::fileView());
	list->setFont(config.font);
	
	list->setSelectionMode(QAbstractItemView::ExtendedSelection);
	list->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
	list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
	list->setResizeMode(QListView::Adjust);
	list->setWrapping(true);
	
	layout->addWidget(list);
	
	connect(list, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(itemDoubleClicked(QListWidgetItem*)));
	connect(list, SIGNAL(itemPressed(QListWidgetItem*)), this, SLOT(itemPressed(QListWidgetItem*)));
	connect(list, SIGNAL(itemSelectionChanged()), this, SLOT(itemSelectionChanged()));
}

void ColumnFileView::itemDoubleClicked(QListWidgetItem *item)
{
	string name = item->text().toStdString();
	string type = fileTypes[name];
	emit doubleClicked(name, type);
}

void ColumnFileView::itemPressed(QListWidgetItem *item)
{
	string name = item->text().toStdString();
	string type = fileTypes[name];
	emit pressed(name, type);
}

void ColumnFileView::itemSelectionChanged()
{
	QList<QListWidgetItem*> selectedItems = list->selectedItems();
	vector<string> fileNames;
	
	for(QList<QListWidgetItem*>::iterator ii=selectedItems.begin(); ii!=selectedItems.end(); ii++)
	{
		QListWidgetItem *item = *ii;
		string name = item->text().toStdString();
		fileNames.push_back(name);
	}
	
	emit selectionChanged(fileNames);
}

void ColumnFileView::filesAdded(const vector<FileEntry> &files)
{
	FileListWidget::filesAdded(files);
	
	for(size_t ii=0; ii<files.size(); ii++)
	{
		const FileEntry *file = &files[ii];
		
		QIcon icon = IconProvider::instance()->getIcon(file, this);
		QListWidgetItem *item = new QListWidgetItem(getFileName(file).c_str());
		item->setIcon(icon);
		
		list->addItem(item);
	}
	
	updateGeometry();
}

